﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;

using RSEngine.Math;

#region Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.

/*
 Copyright (c) 2006, 2007, 2008, 2009 Render Studio Foundation. All rights reserved.
 This work is copyrighted in its entirety. Permission to use "Render Studio"
 material may be granted only after we receive a written request from you,
 and you receive written permission from us.

 While Render Studio Foundation provides the information herein to anyone,
 we retain copyright on all code, text, graphic images, and other content.
 This means that you may not distribute the code, the text or graphics to others without the
 express written permission of Render Studio Foundation; "mirror" this information on your server
 without our permission; or modify or reuse the code, text or graphics on our servers.
 You may print copies of the information for your own personal use; and, you may reference
 the Service from your own documents. Any commercial use of this material is prohibited without
 the written permission of Render Studio Foundation. In all copies of this information, you must
 retain this notice and any other copyright notices originally included with such information.

 This material may be protected by U.S. and International Copyright laws, and you are legally
 bound by the copyright notices on those systems.

 "Render Studio" logos and other official symbols or graphics may not be used in connection with any
 product or service that has not been expressly authorized in advance in writing or in any manner
 that is likely to cause confusion among the users of this or other Web sites or among the public.
 All other seals, graphics or trademarks of organizations not affiliated with Render Studio Foundation
 that appear on this site are the property of their respective owners.

 Render Studio Foundation reserve all other rights. 
*/

#endregion

#region Render Studio Source Code Licence

/*
This license governs use of the Render Studio software. If you use the software, you accept this license. If you do not accept the license, do not use the software.

1. Summary

The source code is made available to view for reference purposes or to contribute to the project only. Developers may not distribute or modify the code for commercial or non-commercial purposes without express Agreement of Render Studio Foundation.

2. Definitions

The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
"Licensor" means the foundation "Render Studio Foundation".
"You" means the licensee of the software, who is not engaged in designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software.
"Your company" means the company you worked for when you downloaded the software.
"Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes of debugging and maintaining your products. For clarity, "reference use" does NOT include (a) the right to use the software for purposes of designing, developing, or testing other software, that has the same or substantially the same features or functionality as the software, and (b) the right to distribute the software outside of your company.
"Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor under this license.

3. Grant of Rights

(A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce the software for reference use.
(B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free patent license under licensed patents for reference use.
4. Limitations
(A) No Trademark License- This license does not grant you any rights to use the Licensor's name, logo, or trademarks.
(B) If you begin patent litigation against the Licensor over patents that you think may apply to the software (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
(C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
 */

#endregion

namespace RSEngine.Core
{
    public enum TransformSpace
    {
        Local,
        Parent,
        World
    }

    /// <summary>
    /// It is the base class for any element that support transformation.
    /// </summary>
    abstract public class Transformable : RSBase, INotifyPropertyChanged
    {

        #region Variables

        //---- Transformations
        public Vector3 Translation = new Vector3();
        public Vector3 Scale = new Vector3(1, 1, 1);
        public Quaternion Orientation = Quaternion.Identity.Clone();

        //---- Working status properties
        private bool _isVisible = true;
        private bool _isLocked = false;
        private bool _isSelected = false;

        //---- Cache
        private bool _mustUpdateLocalTransform = true;
        private Matrix4 _cacheLocalTransform = Matrix4.Identity.Clone();
        private bool _mustUpdateWorldTransform = true;
        private Matrix4 _cacheWorldTransform = Matrix4.Identity.Clone();

        //---- Events

        /// <summary>
        /// Allows to notify that a property has changed
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Constructor

        public Transformable(RSBase parent)
            : base(parent)
        {
        }

        public Transformable(RSBase parent, string name)
            : base(parent, name)
        {
        }

        #endregion

        #region IsVisible / IsLocked

        public bool IsVisible
        {
            get
            {
                return _isVisible;
            }
            set
            {
                if (_isVisible == value)
                    return;
                _isVisible = value;
                OnPropertyChanged("IsVisible");
            }
        }

        public bool IsLocked
        {
            get
            {
                return _isLocked;
            }
            set
            {
                if (_isLocked == value)
                    return;
                _isLocked = value;
                OnPropertyChanged("IsLocked");
            }
        }

        public bool IsSelected
        {
            get
            {
                return _isSelected;
            }
            set
            {
                if (_isSelected == value)
                    return;
                _isSelected = value;
                OnPropertyChanged("IsSelected");
            }
        }

        #endregion

        #region LocalTransform

        /// <summary>
        /// Returns the local transformation matrix
        /// </summary>
        public Matrix4 LocalTransform
        {
            get
            {
                if (_mustUpdateLocalTransform)
                {
                    _cacheLocalTransform = Matrix4.Identity.Clone();

                    // 1 - Scale
                    _cacheLocalTransform.M00 *= Scale.x;
                    _cacheLocalTransform.M11 *= Scale.y;
                    _cacheLocalTransform.M22 *= Scale.z;

                    // 2 - Rotate
                    _cacheLocalTransform = _cacheLocalTransform * Orientation.ToRotationMatrix();

                    // 3 - Translate
                    _cacheLocalTransform.Translation += Translation;

                    _mustUpdateLocalTransform = false;
                }

                return _cacheLocalTransform;
            }
        }

        #endregion

        #region WorldTransform

        /// <summary>
        /// Returns the world transformation matrix
        /// </summary>
        public Matrix4 WorldTransform
        {
            get
            {
                if (_mustUpdateWorldTransform)
                {
                    if (Parent != null && Parent is Transformable)
                        _cacheWorldTransform = ((Transformable)Parent).WorldTransform * LocalTransform;
                    else
                        _cacheWorldTransform = LocalTransform;
                }

                return _cacheWorldTransform;
            }
        }

        #endregion

        #region World transform : V2

        /*
        private Quaternion _worldOrientation;
        private Vector3 _worldScale;
        private Vector3 _worldTranslation;

        public void UpdateWorldTransformation()
        {
            if (Parent != null && Parent is Renderable)
            {
                Renderable parentR = (Renderable)Parent;

                // Insure that the parent is updated
                object dirty = parentR.WorldTransform;

                // combine local orientation with parents
                Quaternion parentOrientation = parentR._worldOrientation;
                _worldOrientation = parentOrientation * _orientation;

                // change position vector based on parent's orientation
                _worldTranslation = parentOrientation * _translation;

                // update scale
                bool inheritsScale = true;
                if (inheritsScale)
                {
                    // set out own position by parent scale
                    Vector3 parentScale = parentR._worldScale;
                    _worldTranslation = _worldTranslation * parentScale;

                    // set own scale, just combine as equivalent axes, no shearing
                    _worldScale = _scale * parentScale;
                }
                else
                {
                    // do not inherit parents scale
                    _worldScale = _scale;
                }

                // add parents positition to local altered position
                _worldTranslation += parentR._worldTranslation;
            }
            else
            {
                // Root node, no parent
                _worldOrientation = _orientation;
                _worldTranslation = _translation;
                _worldScale = _scale;
            }
        }

        public Matrix4 WorldTransform
        {
            get
            {
                if (_mustUpdateWorldTransform)
                {
                    UpdateWorldTransformation();

                    _cacheWorldTransform = Matrix4.Identity.Clone();

                    // 1 - Scale
                    _cacheWorldTransform.M00 *= _worldScale.x;
                    _cacheWorldTransform.M11 *= _worldScale.y;
                    _cacheWorldTransform.M22 *= _worldScale.z;

                    // 2 - Rotate
                    _cacheWorldTransform = _cacheWorldTransform * _worldOrientation.ToRotationMatrix();

                    // 3 - Translate
                    _cacheWorldTransform.Translation += _worldTranslation;

                    _mustUpdateWorldTransform = false;
                }

                return _cacheWorldTransform;
            }
        }
        */

        #endregion

        #region TransformScale

        /// <summary>
        /// Scales the node, combining its current scale with the passed in scaling factor. 
        /// </summary>
        /// <remarks>
        ///	This method applies an extra scaling factor to the node's existing scale, (unlike setScale
        ///	which overwrites it) combining its current scale with the new one. E.g. calling this 
        ///	method twice with Vector3(2,2,2) would have the same effect as setScale(Vector3(4,4,4)) if
        /// the existing scale was 1.
        /// 
        ///	Note that like rotations, scalings are oriented around the node's origin.
        ///</remarks>
        /// <param name="scale"></param>
        public virtual void TransformScale(Vector3 factor)
        {
            Scale *= factor;
            OnUpdateTransform();
        }

        #endregion

        #region TransformRotate

        /// <summary>
        /// Rotate the node around an axis.
        /// </summary>
        public void TransformRotate(Vector3 axis, float radians)
        {
            TransformRotate(axis, radians, TransformSpace.Local);
        }

        /// <summary>
        /// Rotate the node around an arbitrary axis.
        /// </summary>
        public virtual void TransformRotate(Vector3 axis, float radians, TransformSpace relativeTo)
        {
            Quaternion q = Quaternion.FromAngleAxis(axis, radians);
            TransformRotate(q, relativeTo);
        }

        private void TransformRotate(Quaternion q, TransformSpace relativeTo)
        {
            switch (relativeTo)
            {
                case TransformSpace.Parent:
                    // Rotations are normally relative to local axes, transform up
                    Orientation = q * Orientation;
                    break;

                case TransformSpace.World:
                    //_orientation = _orientation * _worldOrientation.Inverse * q * _worldOrientation;
                    break;

                case TransformSpace.Local:
                    // Note the order of the mult, i.e. q comes after
                    Orientation = Orientation * q;
                    break;
            }

            OnUpdateTransform();
        }

        #endregion

        #region TransformTranslate

        /// <summary>
        /// Moves the node along the cartesian axes.
        /// </summary>
        /// <param name="translate">Vector representing the translation.</param>
        public void TransformTranslate(Vector3 translate)
        {
            TransformTranslate(translate, TransformSpace.Parent);
        }

        /// <summary>
        /// Moves the node along the cartesian axes.
        /// </summary>
        /// <param name="translate">Vector representing the translation.</param>
        public void TransformTranslate(Vector3 translate, TransformSpace relativeTo)
        {
            switch (relativeTo)
            {
                case TransformSpace.Local:
                    // position is relative to parent so transform downwards
                    Translation += Orientation * translate;
                    break;

                case TransformSpace.World:
                    //if (Parent != null)
                    //    _translation += _worldOrientation.Inverse * translate;
                    //else
                    //    _translation += translate;

                    break;

                case TransformSpace.Parent:
                    Translation += translate;
                    break;
            }

            OnUpdateTransform();
        }

        #endregion

        #region OnUpdateTransform

        /// <summary>
        /// Update the transformation information for
        /// this instance and all its childs.
        /// </summary>
        virtual public void OnUpdateTransform()
        {
            _mustUpdateWorldTransform = true;
            _mustUpdateLocalTransform = true;
        }

        #endregion

        #region OnPropertyChanged

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region To implement ...

        /*
        virtual internal void UpdateFromParent()
        {
            if (parent != null)
            {
                // combine local orientation with parents
                Quaternion parentOrientation = parent.DerivedOrientation;
                derivedOrientation = parentOrientation * orientation;

                // change position vector based on parent's orientation
                derivedPosition = parentOrientation * position;

                // update scale
                if (inheritsScale)
                {
                    // set out own position by parent scale
                    Vector3 parentScale = parent.DerivedScale;
                    derivedPosition = derivedPosition * parentScale;

                    // set own scale, just combine as equivalent axes, no shearing
                    derivedScale = scale * parentScale;
                }
                else
                {
                    // do not inherit parents scale
                    derivedScale = scale;
                }

                // add parents positition to local altered position
                derivedPosition += parent.DerivedPosition;
            }
            else
            {
                // Root node, no parent
                derivedOrientation = orientation;
                derivedPosition = position;
                derivedScale = scale;
            }

            needTransformUpdate = true;
            needRelativeTransformUpdate = true;
            if (suppressUpdateEvent == false)
            {
                OnUpdatedFromParent();
            }
        }

        /// <summary>
        /// Moves the node along arbitrary axes.
        /// </summary>
        /// <remarks>
        ///	This method translates the node by a vector which is relative to
        ///	a custom set of axes.
        ///	</remarks>
        /// <param name="axes">3x3 Matrix containg 3 column vectors each representing the
        ///	X, Y and Z axes respectively. In this format the standard cartesian axes would be expressed as:
        ///		1 0 0
        ///		0 1 0
        ///		0 0 1
        ///		i.e. The Identity matrix.
        ///	</param>
        /// <param name="move">Vector relative to the supplied axes.</param>
        public virtual void Translate(Matrix3 axes, Vector3 move)
        {
            Vector3 derived = axes * move;
            Translate(derived, TransformSpace.Parent);
        }

        /// <summary>
        /// Moves the node along arbitrary axes.
        /// </summary>
        /// <remarks>
        ///	This method translates the node by a vector which is relative to
        ///	a custom set of axes.
        ///	</remarks>
        /// <param name="axes">3x3 Matrix containg 3 column vectors each representing the
        ///	X, Y and Z axes respectively. In this format the standard cartesian axes would be expressed as:
        ///		1 0 0
        ///		0 1 0
        ///		0 0 1
        ///		i.e. The Identity matrix.
        ///	</param>
        /// <param name="move">Vector relative to the supplied axes.</param>
        public virtual void Translate(Matrix3 axes, Vector3 move, TransformSpace relativeTo)
        {
            Vector3 derived = axes * move;
            Translate(derived, relativeTo);
        }

        /// <summary>
        /// Rotate the node around the X-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Pitch(float degrees, TransformSpace relativeTo)
        {
            Rotate(Vector3.UnitX, degrees, relativeTo);
        }

        /// <summary>
        /// Rotate the node around the X-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Pitch(float degrees)
        {
            Rotate(Vector3.UnitX, degrees, TransformSpace.Local);
        }

        /// <summary>
        /// Rotate the node around the Z-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Roll(float degrees, TransformSpace relativeTo)
        {
            Rotate(Vector3.UnitZ, degrees, relativeTo);
        }

        /// <summary>
        /// Rotate the node around the Z-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Roll(float degrees)
        {
            Rotate(Vector3.UnitZ, degrees, TransformSpace.Local);
        }

        /// <summary>
        /// Rotate the node around the Y-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Yaw(float degrees, TransformSpace relativeTo)
        {
            Rotate(Vector3.UnitY, degrees, relativeTo);
        }

        /// <summary>
        /// Rotate the node around the Y-axis.
        /// </summary>
        /// <param name="degrees"></param>
        public virtual void Yaw(float degrees)
        {
            Rotate(Vector3.UnitY, degrees, TransformSpace.Local);
        }

        /// <summary>
        /// Rotate the node around an arbitrary axis.
        /// </summary>
        public virtual void Rotate(Vector3 axis, float degrees, TransformSpace relativeTo)
        {
            Quaternion q = Quaternion.FromAngleAxis(Utility.DegreesToRadians(degrees), axis);
            Rotate(q, relativeTo);
        }

        /// <summary>
        /// Rotate the node around an arbitrary axis.
        /// </summary>
        public virtual void Rotate(Vector3 axis, float degrees)
        {
            Rotate(axis, degrees, TransformSpace.Local);
        }

        /// <summary>
        /// Rotate the node around an arbitrary axis using a Quaternion.
        /// </summary>
        public virtual void Rotate(Quaternion rotation, TransformSpace relativeTo)
        {
            switch (relativeTo)
            {
                case TransformSpace.Parent:
                    // Rotations are normally relative to local axes, transform up
                    orientation = rotation * orientation;
                    break;

                case TransformSpace.World:
                    orientation = orientation * DerivedOrientation.Inverse() * rotation * DerivedOrientation;
                    break;

                case TransformSpace.Local:
                    // Note the order of the mult, i.e. q comes after
                    orientation = orientation * rotation;
                    break;
            }

            NeedUpdate();
        }

        /// <summary>
        /// Rotate the node around an arbitrary axis using a Quaternion.
        /// </summary>
        public virtual void Rotate(Quaternion rotation)
        {
            Rotate(rotation, TransformSpace.Local);
        }

        /// <summary>
        /// Resets the nodes orientation (local axes as world axes, no rotation).
        /// </summary>
        public virtual void ResetOrientation()
        {
            orientation = Quaternion.Identity;
            NeedUpdate();
        }

        /// <summary>
        /// Resets the position / orientation / scale of this node to its initial state, see SetInitialState for more info.
        /// </summary>
        public virtual void ResetToInitialState()
        {
            position = initialPosition;
            orientation = initialOrientation;
            scale = initialScale;

            // Reset weights
            accumAnimWeight = 0.0f;
            translationFromInitial = Vector3.Zero;
            rotationFromInitial = Quaternion.Identity;
            scaleFromInitial = Vector3.UnitScale;

            NeedUpdate();
        }

        /// <summary>
        /// Sets the current transform of this node to be the 'initial state' ie that
        ///	position / orientation / scale to be used as a basis for delta values used
        /// in keyframe animation.
        /// </summary>
        /// <remarks>
        ///	You never need to call this method unless you plan to animate this node. If you do
        ///	plan to animate it, call this method once you've loaded the node with its base state,
        ///	ie the state on which all keyframes are based.
        ///
        ///	If you never call this method, the initial state is the identity transform (do nothing) and a position of zero
        /// </remarks>
        public virtual void SetInitialState()
        {
            initialOrientation = orientation;
            initialPosition = position;
            initialScale = scale;
        }
        */

        #endregion

        #region Clone

        protected void Clone(Transformable toClone)
        {
            base.Clone(toClone);

            toClone.Translation = Translation;
            toClone.Scale = Scale;
            toClone.Orientation = Orientation;

            toClone._isVisible = _isVisible;
            toClone._isLocked = _isLocked;

            toClone._mustUpdateLocalTransform = _mustUpdateLocalTransform;
            toClone._cacheLocalTransform = _cacheLocalTransform.Clone();
            toClone._mustUpdateWorldTransform = _mustUpdateWorldTransform;
            toClone._cacheWorldTransform = _cacheWorldTransform.Clone();
        }

        #endregion

    }
}
